package com.controls
{
    import flash.events.MouseEvent;
    
    import mx.controls.List;
    import mx.controls.listClasses.IListItemRenderer;
    import mx.core.mx_internal;
    import mx.events.DragEvent;
    
    use namespace mx_internal;

    public class DragSelectList extends List
    {
        /**
         * @private
         * Boolean to store whether we are in the middle of a dragging selection event.
         */
        private var dragSelecting:Boolean = false;
        
        /**
         * @private
         * The item renderer of the item that was clicked that started the drag selection event.
         */
        private var dragStartItem:IListItemRenderer;
        
        /**
         * @private
         * The data provider index of the item that started the dragging.
         */
        private var dragStartIndex:int;
        
        /**
         * @private
         * Used to store the last index that was dragged over.
         */
        private var lastDragIndex:int;
        
        /**
         * @private
         * 
         * We have to alter the selectItem functionality. If we're in the middle of a dragging event then we
         * just call selectItem in the ListBase class like normal. But if we're not dragging then if they shift 
         * key isn't held down then we fake that the user is holding down the control key. So the defaul selection 
         * behavior is as if the user was holding control. 
         */
        override protected function selectItem(item:IListItemRenderer, shiftKey:Boolean, ctrlKey:Boolean, transition:Boolean=true):Boolean {
            if(dragSelecting) {
                return super.selectItem(item, shiftKey, ctrlKey, transition);
            }
            else {
                if(shiftKey) {
                    return super.selectItem(item, true, false, transition);
                }
                else {
                    return super.selectItem(item, false, true, transition);
                }
            }
        }
        
        /**
         * @private
         * 
         * We check if the item they pressed is selected. If it is then we start the dragging selection.
         */
        override protected function mouseDownHandler(event:MouseEvent):void {
            dragStartItem = mouseEventToItemRenderer(event);
            var alreadySelected:Boolean = dragStartItem && isItemSelected(dragStartItem.data);
            
            super.mouseDownHandler(event);
            
            if(dragStartItem && !alreadySelected) {
                dragStartIndex = itemRendererToIndex(dragStartItem);
                lastDragIndex = dragStartIndex;
                
                dragSelecting = true;
            }
            else {
                dragSelecting = false;
            }
        }
        
        /**
         * @private
         * 
         * We stop the dragging selection, and we also stop the scrolling if it's happening.
         */
        override protected function mouseUpHandler(event:MouseEvent):void {
            super.mouseUpHandler(event);
    
            resetDragScrolling();
            
            dragSelecting = false;
        }
        
        /**
         * @private
         * 
         * Handles the dragging functionality to select and/or deselect items based on where the user
         * originally clicked to start the dragging operation.
         */
        override protected function mouseMoveHandler(event:MouseEvent):void {
            
            // We need to call ListBase.mouseMoveHandler(), but first we need to check 
            // if we're doing a drag-selection. If we are then we first pretend like dragging is disabled,
            // so we don't start dragging the selected items as part of the normal drag-drop functionality.
            if(dragSelecting) {
                var oldDragEnabled:Boolean = dragEnabled;
                
                // we fake setting dragEnabled to false so the call to super.mouseMoveHandler doesn't
                // start dragging the selected items.
                dragEnabled = false;
                super.mouseMoveHandler(event);
                
                //then once super.mouseMoveHandler is done we can set dragEnabled back to whatever it was
                dragEnabled = oldDragEnabled;    
            }
            else {
                // if we're not dragging then the normal mouseMoveHandler if ListBase is fine
                super.mouseMoveHandler(event);
            }
            
            // if we're not dragging then we don't need to do anything else
            if(!dragSelecting || !dragStartItem) return;
            
            // if we are dragging then we figure out which item we're currently over
            var item:IListItemRenderer = mouseEventToItemRenderer(event);
            
            // we only want to do stuff if we're over a renderer (and not the renderer they clicked)
            if(item && item != dragStartItem) {
                var index:int = itemRendererToIndex(item);
                
                var i:int;
                
                // if the item is above the clicked item in the list
                if(index < dragStartIndex) {
                    // we loop through all the items between the current one and the clicked one
                    // and select them all
                    for(i=index; i<=dragStartIndex; i++) {
                        selectItemAt(i);
                    }
                
                    // the loop below handles the case where we have dragged to make a selection and then
                    // we drag back and we want to deselect some if the items we selected before
                    if(index > lastDragIndex) {
                        for(i=lastDragIndex; i<index; i++) {
                            deselectItemAt(i);
                        }
                    }
                }
                else {
                    // loop over all the items after the initially clicked item until we get to the current
                    // item that the mouse is over
                    for(i=dragStartIndex + 1; i<=index; i++) {
                        selectItemAt(i);
                    }
                    
                    if(index < lastDragIndex) {
                        for(i=index+1; i<=lastDragIndex; i++) {
                            deselectItemAt(i);
                        }
                    }
                }
                
                lastDragIndex = index;
            }
            else if(item && item == dragStartItem) {
                // if we've moved back onto the original item then that means we probably need to deselect
                // whichever item we just rolled off of.
                if(lastDragIndex != itemRendererToIndex(item)) {
                    deselectItemAt(lastDragIndex);
                }
            }
            
            //calling dragScroll allows us to scroll the list into view if we drag below or above the list
            dragScroll();
            
        }
        
        /**
         * @private
         * 
         * Selects an item. This only works for visible items with an item renderer. I couldn't quite figure out how 
         * you're supposed to select an item that is not visible. Something about setting selectedData, but I ran into a few
         * bugs with that, so decided to not even bother.
         */
        private function selectItemAt(index:int):void {
            var curItem:IListItemRenderer = indexToItemRenderer(index);
    
            if(curItem != null) {
                if(!isItemSelected(curItem.data)) {
                    super.selectItem(curItem, false, true, false);
                }
            }
        }
        
        /**
         * @private
         * 
         * I figured out how to deselect items not visible in the list, just set the right item in
         * the selectedData array to undefined.
         */
        private function deselectItemAt(index:int):void {
            var curItem:IListItemRenderer = indexToItemRenderer(index);
            
            // there are two options, either the item is visible, in which case there is an item renderer, or it's
            // not and there is no item renderer for the item. If there's an item renderer then we just call selectItem
            if(curItem != null) {
                if(isItemSelected(curItem.data)) {
                    super.selectItem(curItem, false, true, false);
                }
            }
            else {
                // if the item is not visible then we just set the item in the selectedData array
                selectedData[itemToUID(dataProvider[index])] = undefined;
            }
        }
    }
}